#ifndef TEST_CASE_H_
#define TEST_CASE_H_

#include <assert.h>
#include <time.h>

#include "../Solver/Solver.h"

class TestCase {
private:
    static AdjacentMatrix *generateSmallRandomRawGraph(int n = 10) {
        int m = n * (n - 1);
        AdjacentMatrix::Arch* archArray = (AdjacentMatrix::Arch*) malloc(sizeof(AdjacentMatrix::Arch) * m);
        assert(archArray!=NULL);
        int top = 0;
        for (int i = 0; i < n; ++i)
            for (int j = i + 1; j < n; ++j) {
                int w = rand() % 10 + 1;
                archArray[top].i = i;
                archArray[top].j = j;
                archArray[top].w = w;
                ++top;
                archArray[top].i = j;
                archArray[top].j = i;
                archArray[top].w = w;
                ++top;
            }
        AdjacentMatrix *ret = new AdjacentMatrix(n, m, archArray);
        free(archArray);
        return ret;
    }

    static void runTest(CacheFriendlyAdjacentMatrix &g2, Colors *colors, int source, Solver<AdjacentMatrix> *solver, bool isFirstSolver = true, bool isDebug = false) {
        static int first = -1, ret = -1;

        for (int i = 0; i < 2; ++i) {
            double prepareTimeS, runningTimeS, totalScan, avgScan;
            solver->compute(g2, colors, 0, prepareTimeS, runningTimeS, totalScan, avgScan);
            printf("-------------CacheFriendlyAdjacentMatrix---------------\n");
            printf("%s\n\tn=%d m=%d\n\tprepareTimeS=%lf runningTimeS=%lf checksum=%08x\n\t\ttotalScan=%.0lf avgScan=%.1lf\n", solver->getName(), g2.n, g2.m, prepareTimeS, runningTimeS, ret = g2.checkSum(), totalScan, avgScan);

            if (isDebug) g2.showResult();
            printf("\n");

            fflush(stdout);
            if (isFirstSolver)
                first = ret;
            else
                assert(first == ret);
        }
    }

    static void runTest(AdjacentMatrix &g2, Colors *colors, int source, Solver<AdjacentMatrix> *solver, bool isFirstSolver = true, bool isDebug = false) {
        static int first = -1, ret = -1;

        for (int i = 0; i < 2; ++i) {
            double prepareTimeS, runningTimeS, totalScan, avgScan;
            solver->compute(g2, colors, 0, prepareTimeS, runningTimeS, totalScan, avgScan);
            printf("-------------AdjacentMatrix---------------\n");
            printf("%s\n\tn=%d m=%d\n\tprepareTimeS=%lf runningTimeS=%lf checksum=%08x\n\t\ttotalScan=%.0lf avgScan=%.1lf\n", solver->getName(), g2.n, g2.m, prepareTimeS, runningTimeS, ret = g2.checkSum(), totalScan, avgScan);

            if (isDebug) g2.showResult();
            printf("\n");

            fflush(stdout);
            if (isFirstSolver)
                first = ret;
            else
                assert(first == ret);
        }
    }

public:

    static void testSmallRandomRawGraph() {
        srand(0);
        puts("test Small random graph");
        for (int times = 0; times < 1000; ++times) {
            printf("test No=%d\n", times);
            const int THREAD_NUM = 4;
            AdjacentMatrix *graph = generateSmallRandomRawGraph();
            graph->print();
            Colors colors, colors2;
            colors.color_BFS_DeltaDepth(*graph, 0, 1);
            colors.refine(THREAD_NUM);
            colors.print();

            colors2.color_BFS_DeltaDepth(*graph, 0, 1);
            colors2.refine(THREAD_NUM);
            CacheFriendlyAdjacentMatrix graph2(graph->n, graph->m, graph->archArray);
            reorg(graph2, colors2);
            colors2.print();

            {
                DijstraHeap solver;
                runTest(*graph, &colors, 0, &solver, true, true);
                runTest(graph2, &colors2, 0, &solver, true, true);
            }
            {
                AdjustDij_NoGlobalSync solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }
            {
                AdjustDij_GlobalHeapStack solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }
            {
                AdjustDij_NoLock solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }
            {
                AdjustDij_Timer solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }
            {
                //AdjustDij_NoAtomic fails when dataset is small
                //AdjustDij_NoAtomic solver(THREAD_NUM, 2);
                //runTest(*graph, &colors, 0, &solver, false, true);
            }
            {
                AdjustDij_NoAtomic2 solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }
            {
                AdjustDij_NoAtomic3 solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }
            {
                AdjustDij_NoAtomic4 solver(THREAD_NUM, 2);
                runTest(*graph, &colors, 0, &solver, false, true);
                runTest(graph2, &colors2, 0, &solver, false, true);
            }

            delete graph;
        }
    }

    static void testAdjustDij(const char * DATA_SRC) {
        const int THREAD_NUM = 8;

        AdjacentMatrix g(DATA_SRC);

        {
            DijstraHeap solver;
            runTest(g, NULL, 0, &solver, true, false);
        }

        CacheFriendlyAdjacentMatrix g2(g.n, g.m, g.archArray);

        for (int DELTA_DEPTH = 2 << 4; DELTA_DEPTH <= 2 << 4; DELTA_DEPTH <<= 1) {
            printf("\nDELTA_DEPTH=%d\n", DELTA_DEPTH);

            Colors colors;
            colors.color_BFS_DeltaDepth(g, 0, DELTA_DEPTH);
            colors.refine(THREAD_NUM);

            Colors colors2;
            colors2.color_BFS_DeltaDepth(g, 0, DELTA_DEPTH);
            colors2.refine(THREAD_NUM);
            reorg(g2, colors2);

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; num_relax_each_iter *= 2) {
                AdjustDij_NoGlobalSync solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, true, false);
            }

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; num_relax_each_iter *= 2) {
                AdjustDij_GlobalHeapStack solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, false, false);
            }

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
                AdjustDij_NoLock solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, false, false);
            }

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
                AdjustDij_NoAtomic solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, false, false);
            }

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
                AdjustDij_NoAtomic2 solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, false, false);
            }

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
                AdjustDij_NoAtomic3 solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, false, false);
            }

            for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
                AdjustDij_NoAtomic4 solver(THREAD_NUM, num_relax_each_iter);
                runTest(g, &colors, 0, &solver, false, false);
                runTest(g2, &colors2, 0, &solver, false, false);
            }
        }
    }

private:
    static void testAdjustDijSpeed(const char * DATA_SRC2, int THREAD_NUM, AdjacentMatrix &g) {
        Colors colors;
        colors.readFromObjectFile(g, DATA_SRC2);
        colors.refine(THREAD_NUM);

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; num_relax_each_iter *= 2) {
            AdjustDij_GlobalHeapStack solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, true, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoLock solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic2 solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic3 solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic4 solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }
    }

public:
    static void testAdjustDijSpeed(const char * DATA_SRC, const char * DATA_SRC2) {
        AdjacentMatrix g(DATA_SRC);

#ifndef IS_MIC
        testAdjustDijSpeed(DATA_SRC2, 4, g);
        testAdjustDijSpeed(DATA_SRC2, 8, g);
#else
        testAdjustDijSpeed(DATA_SRC2, 60, g);
        testAdjustDijSpeed(DATA_SRC2, 120, g);
        testAdjustDijSpeed(DATA_SRC2, 240, g);
#endif
    }

    static void testAdjustDijSpeedCachedFriendly(const char * DATA_SRC, const char * DATA_SRC2, int THREAD_NUM) {
        CacheFriendlyAdjacentMatrix g(DATA_SRC);
        Colors colors;
        colors.readFromObjectFile(g, DATA_SRC2);
        colors.refine(THREAD_NUM);
        reorg(g, colors);

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; num_relax_each_iter *= 2) {
            AdjustDij_GlobalHeapStack solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, true, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoLock solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic2 solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic3 solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }

        for (int num_relax_each_iter = 32; num_relax_each_iter <= 32; /*65536;*/num_relax_each_iter *= 2) {
            AdjustDij_NoAtomic4 solver(THREAD_NUM, num_relax_each_iter);
            runTest(g, &colors, 0, &solver, false, false);
        }
    }

    static void testAdjustDijSpeedCachedFriendly(const char * DATA_SRC, const char * DATA_SRC2) {
#ifndef IS_MIC
        testAdjustDijSpeedCachedFriendly(DATA_SRC, DATA_SRC2, 4);
        testAdjustDijSpeedCachedFriendly(DATA_SRC, DATA_SRC2, 8);
#else
        testAdjustDijSpeedCachedFriendly(DATA_SRC, DATA_SRC2, 60);
        testAdjustDijSpeedCachedFriendly(DATA_SRC, DATA_SRC2, 120);
        testAdjustDijSpeedCachedFriendly(DATA_SRC, DATA_SRC2, 240);
#endif
    }

    static void testClock() {
        omp_lock_t lock;
        omp_init_lock(&lock);

        const int THREAD_NUM = 8;
        omp_set_num_threads(THREAD_NUM);

        CycleTimer mainTimer;
        mainTimer.Start();

#pragma omp parallel
        {
            CycleTimer localTimer;
            localTimer.Start(&mainTimer.getStart());
            const int pid = omp_get_thread_num();
            for (int i = 0; i < 10; ++i) {
                omp_set_lock(&lock);
                printf("pid=%d, clock()=%ld, timer=%lld\n", pid, clock(), localTimer.getTimeFromStart());
                fflush(stdout);
                omp_unset_lock(&lock);
            }
        }

        omp_destroy_lock(&lock);
    }

    enum SOLVER_TYPE {
        ST_DIJ_HEAP, ST_OMP_LOCK_EXTRA_MEM, ST_OMP_LOCK, ST_GLOBAL_VERSION, ST_TIMER_VERSION, ST_PROTOCOL, ST_PROTOCOL2
    };

    static void singleTest(const SOLVER_TYPE stype, const int THREAD_NUM = 8, const int num_relax_each_iter = 64) {
        AdjacentMatrix g(Dataset::DATA_CTR_OBJ);
        Colors colors;
        colors.readFromObjectFile(g, Dataset::DATA_CTR_COLOR64);
        colors.refine(THREAD_NUM);

        Solver<AdjacentMatrix> *pSolver = NULL;
        AdjustDij_GlobalHeapStack solver(THREAD_NUM, num_relax_each_iter);
        switch (stype) {
            case ST_DIJ_HEAP:
                pSolver = new DijstraHeap();
                break;
            case ST_OMP_LOCK_EXTRA_MEM:
                pSolver = new AdjustDij_NoGlobalSync(THREAD_NUM, num_relax_each_iter);
                break;
            case ST_OMP_LOCK:
                pSolver = new AdjustDij_GlobalHeapStack(THREAD_NUM, num_relax_each_iter);
                break;
            case ST_GLOBAL_VERSION:
                pSolver = new AdjustDij_NoLock(THREAD_NUM, num_relax_each_iter);
                break;
            case ST_TIMER_VERSION:
                pSolver = new AdjustDij_Timer(THREAD_NUM, num_relax_each_iter);
                break;
            case ST_PROTOCOL:
                pSolver = new AdjustDij_NoAtomic(THREAD_NUM, num_relax_each_iter);
                break;
            case ST_PROTOCOL2:
                pSolver = new AdjustDij_NoAtomic2(THREAD_NUM, num_relax_each_iter);
                break;
        }
        assert(pSolver!=NULL);
        runTest(g, &colors, 0, pSolver, true, false);

        delete pSolver;
    }
};
#endif
